<?php

/**
 * Based on http://www.longshadow.com.au/tying-the-acl-to-controllersaction-in-zend-framework-15/
 *
 */
class AuthorizationPlugin extends Zend_Controller_Plugin_Abstract
{
	protected $_acl;
	protected $_rights;
	
	public function __construct()
    {
    	// Create intances of Acl and rights
    	$this->_acl = new Zend_Acl();
    	$this->_rights = new Rights();
    	
    	$this->loadRights()
    	     ->buildGroupAcl()
    	     ->buildUserAcl();
    	     
    	Zend_Registry::getInstance()->set("acl", $this->_acl);
    	Zend_Registry::getInstance()->set("rights", $this->_rights);
    }
    
    
    
    /**
     * Called after the router finishes routing the request. 
     * 
     * This will redirect user to a access-denied action on the index controller 
     * if he is not allowed here.
     *
     * @param Zend_Controller_Request_Abstract $request
     */
    public function routeShutdown(Zend_Controller_Request_Abstract $request)
    {
        // Don't authorize on non-existent paths, this way a proper exception will
        // reach the error controller instead.
        if (!$this->doesRouteExist($request)) {
            return;
        }
        
    	$requestParams = $request->getParams();
    	
    	// Get the current request and convert it to ACL terms
        $resource = $requestParams['module']."/".$requestParams['controller'];
        $privledge = $requestParams['action'];
        
        // Test if the user is allowed here (asking on a undefined resource trigger and expection)
        try {
    		$allowed = $this->_acl->isAllowed("user", $resource, $privledge);
        } catch (Zend_Acl_Exception $e) {
        	$allowed = false;
        }
    	
    	if (!$allowed) {
    	    
            $redirector = Zend_Controller_Action_HelperBroker::getStaticHelper('redirector'); 
             
    	    if ($request->isXmlHttpRequest()) {
    	        // Im afraid this is the best i can come up with ...
                echo Zend_Json::encode(array("status"=>400, "message"=>"Access denied for action:\n" . $request->getPathInfo()));  
                exit(); 
    	    } else {
                $redirector->setGotoSimple("access-denied", "index", null, array("redirect"=>urlencode($request->getPathInfo()))); 
    	    }
    	}
    }
    
    /**
     * Loads the xml file containing the rights.
     *
     * @return AuthorizationPlugin
     */
    protected function loadRights()
    {
    	// Load the rights from the file
    	$rightFile = Zend_Registry::getInstance()->configuration->rights->file;
    	$this->_rights->load($rightFile);
    	return $this;
    }
    
    /**
     * Fill the Acl with information about Groups and their privledges.
     *
     * @return AuthorizationPlugin
     */
    protected function buildGroupAcl()
    {
    	// Get the needed data sources
    	$groupTable = new Groups();

        // Fecth all groups and process them
		$groups = $groupTable->fetchAll();
		
		foreach($groups as $group) {
			
			// Create a role for every group
			$this->_acl->addRole(new Zend_Acl_Role($group->name));

			// Get the grants associated with the group
			foreach( $group->findGrants() as $grant) {
				
				// Get the right associated with the handle 
				// (and check if it exists)
				$right = $this->_rights->getRight($grant->handle);
				if (!$right) {
					continue;
				}
				
				// Process allows associated with the right
				foreach( $right->allows as $allow) {
					
					// Resource
					$resource = null;
					if ($allow['module'] && $allow['controller']) {
						$resource = $allow['module']."/".$allow['controller'];
					}
					
					// Privlegde
					$privlegde = $allow['action'];
					
					// Add resources if they dont exists
					if ($resource && !$this->_acl->has($resource)) {
						$this->_acl->add(new Zend_Acl_Resource($resource)); 
					}
						
					// Add the allow
					$this->_acl->allow($group->name, $resource, $privlegde);
				}		
			}
		}
		
       	return $this;
    }
    
    /**
     * Fill the Acl with information based on the current user.
     *
     * @return AuthorizationPlugin
     */
    protected function buildUserAcl()
    {
    	// Group table
    	$groupTable = new Groups();
    	
		// Get the Zend Auth component
     	$auth = Zend_Auth::getInstance();
     	
     	// Holds the users groups
     	$groups = array();
     	
     	// Everyone is a member of the default groups
     	$defaultRowset = $groupTable->fetchAll("def = true");
     	foreach($defaultRowset as $group) {
     		$groups[] = $group->name;
     	}
     	
     	// Is the user authed
        if ($auth->hasIdentity()) {
        	// Get all groups that user is directly a member of
            $usersTable = new Users();
            $user = $usersTable->fetchRow("id = ".$auth->getIdentity()->id);
            $groupsRowset = $user->findGroupsViaUsersGroups();
            foreach($groupsRowset as $group) {
            	$groups[] = $group->name;
            }
        }
        
        // Remove group duplicates (yeah, well :)
        array_unique($groups);
        
        // Setup the user roles
        // This line means that user extends whatever groups he may be in
        // thereby getting obtaining there allows ...
        $this->_acl->addRole(new Zend_Acl_Role("user"), $groups);
        
        return $this;

		/*
        // Get the current request and convert it to ACL terms
        $request = $this->getRequest()->getParams();
        $resource = $request['module']."/".$request['controller'];
        $privledge = $request['action'];
        
        // Make sure that the resource is defined, in case its not included in the rights list
        // Otherwise isAllowed will trigger a error
        if (!$acl->has($resource)) $acl->add(new Zend_Acl_Resource($resource)); 
		*/
    }
    
    protected function doesRouteExist(Zend_Controller_Request_Abstract $request)
    {
        // Test if the controller/action exist
        $dispatcher = Zend_Controller_Front::getInstance()->getDispatcher();
        
        // Get get the class name based on the controller
        $controllerName = $request->getControllerName();
        if (empty($controllerName)) {
            $controllerName = $dispatcher->getDefaultController();
        }
        $className = $dispatcher->formatControllerName($controllerName);
        
        if ($className) {
            try {
                @(Zend_Loader::loadClass($className, $dispatcher->getControllerDirectory()));
                
                // Get the method name based on the action
                $actionName = $request->getActionName();
                if (empty($actionName)) {
                    $actionName = $dispatcher->getDefaultAction();
                }
                $methodName = $dispatcher->formatActionName($actionName);
                $class = new ReflectionClass( $className );
                return $class->hasMethod( $methodName );
                
            } catch (Zend_Exception $e) {
                // This means that the class could not be loaded
                // eg. it does not exist
            }
        }
        
        return false;
    }

}